package models

import (
	"bigdevops/src/common"
	"encoding/json"
	"fmt"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	corev1 "k8s.io/api/core/v1"
)

/*

   containers:
	- command:
	  - sh
	  - -c
	  - echo The app is running! && sleep infinity
	  image: busybox
	  imagePullPolicy: Always

   - args:
     - --cert-dir=/tmp
     - --secure-port=4443
     - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
     - --kubelet-use-node-status-port
     - --metric-resolution=15s
     - --kubelet-insecure-tls
     image: registry.cn-beijing.aliyuncs.com/ning1875_haiwai_image/metrics-server:v0.6.4
     imagePullPolicy: IfNotPresent
     livenessProbe:
       failureThreshold: 3
       httpGet:
         path: /livez
         port: https
         scheme: HTTPS
       periodSeconds: 10
       successThreshold: 1
       timeoutSeconds: 1
     name: metrics-server
     ports:
     - containerPort: 4443
       name: https
       protocol: TCP
     readinessProbe:
       failureThreshold: 3
       httpGet:
         path: /readyz
         port: https
         scheme: HTTPS
       initialDelaySeconds: 20
       periodSeconds: 10
       successThreshold: 1
       timeoutSeconds: 1
     resources:
       requests:
         cpu: 100m
         memory: 200Mi
     securityContext:
       allowPrivilegeEscalation: false
       readOnlyRootFilesystem: true
       runAsNonRoot: true
       runAsUser: 1000
     terminationMessagePath: /dev/termination-log
     terminationMessagePolicy: File
     volumeMounts:
     - mountPath: /tmp
       name: tmp-dir
   dnsPolicy: ClusterFirst
   nodeSelector:
     kubernetes.io/os: linux
   priorityClassName: system-cluster-critical
   restartPolicy: Always
   schedulerName: default-scheduler
   securityContext: {}
   serviceAccount: metrics-server
   serviceAccountName: metrics-server
   terminationGracePeriodSeconds: 30
   volumes:
   - emptyDir: {}
     name: tmp-dir

*/

// 面向运维的k8s项目
type K8sApp struct {
	Model               // 不用每次写ID 和 createAt了
	Name         string `json:"name" validate:"required,min=1,max=200" gorm:"uniqueIndex:name_cluster;type:varchar(100);comment:k8s英文集群名称，支持使用通配符*进行模糊搜索" `
	K8sProjectID uint   // 属于哪个项目 一对多的外键
	TreeNodeId   uint   `json:"treeNodeId" gorm:"comment:绑定到哪个节点的 面向第四层级"`
	UserID       uint

	Cluster string `json:"cluster" gorm:"uniqueIndex:name_cluster;type:varchar(100);comment:k8s英文集群名称，支持使用通配符*进行模糊搜索" `
	// 一对多关系 https://gorm.io/zh_CN/docs/has_many.html
	K8sInstances []K8sInstance

	// clusterIp or nodePort
	ServiceType string `json:"serviceType"`
	// 下面都是Deployment的字段
	// 当然不可以直接拿Deployment spec字段过来： gorm不支持 ，
	// 主要目的是简化用户配置Deployment 的过程。所以肯定不会让它全部填yaml写入
	Namespace string `json:"namespace"`

	ContainerCore
	StreeNodeObj   *StreeNode  `json:"streeNodeObj" gorm:"-"`
	ClusterObj     *K8sCluster `json:"clusterObj"  gorm:"-"`
	ProjectObj     *K8sProject `json:"projectObj" gorm:"-"`
	CreateUserName string      `json:"createUserName" gorm:"-"`
	NodePath       string      `json:"nodePath" gorm:"-"`       // a.b.c.d
	K8sProjectName string      `json:"k8SProjectName" gorm:"-"` // a.b.c.d
	Key            string      `json:"key" gorm:"-"`            //给前端表格

}

// 这里抽取出来的目的是 app和instance 都要使用 ，类似Model 会被所有的结构使用
type ContainerCore struct {
	// 容器字段
	Envs     StringArray `json:"envs" gorm:"comment: 环境变量组 key=v ,scrape_ip=1.1.1.1 "`
	Labels   StringArray `json:"labels" gorm:"comment: 标签组 key=v ,scrape_ip=1.1.1.1 "`
	Commands StringArray `json:"commands" gorm:"comment: 启动命令组 [] "`
	Args     StringArray `json:"args" gorm:"comment: 参数空格分割 --cert-dir=/tmp --secure-port=4443"`

	EnvsFront     []common.FromKvOneItem `json:"envsFront" gorm:"-"`
	LabelsFront   []common.FromKvOneItem `json:"labelsFront" gorm:"-"`
	CommandsFront []common.FromKvOneItem `json:"commandsFront" gorm:"-"`
	ArgsFront     []common.FromKvOneItem `json:"argsFront" gorm:"-"`

	// 资源 有可能不放开给用户填：有可能填错了
	CpuRequest    string `json:"cpuRequest"`
	CpuLimit      string `json:"cpuLimit"`
	MemoryRequest string `json:"memoryRequest"`
	MemoryLimit   string `json:"memoryLimit"`

	// 挂载卷 https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/
	// 他是 []OneVolume 的json
	VolumeJson      string      `json:"volumeJson" gorm:"type:text;comment:卷和挂载配置json"`
	VolumeJsonFront []OneVolume `json:"volumeJsonFront" gorm:"-"`

	// 端口 直接用 []corev1.servicePort
	PortJson      string               `json:"portJson" gorm:"type:text;comment: 容器和svc端口配置"`
	PortJsonFront []corev1.ServicePort `json:"portJsonFront" gorm:"-"`
}

type OneVolume struct {
	// hostPath configmap emptydir pvc
	Type      string `json:"type"`
	Name      string `json:"name"`
	MountPath string `json:"mountPath"`
	SubPath   string `json:"subPath"`
	// 类型为pvc时才有
	PvcName      string `json:"pvcName"`
	CmName       string `json:"cmName"`
	HostPathPath string `json:"hostPathPath"`
	HostPathType string `json:"hostPathType"`
}

type OneResource struct {
	Request string `json:"request"`
}

func DeleteK8sAppOneByInstanceId(iid string) error {

	return Db.Select(clause.Associations).Unscoped().Where("instance_id = ?", iid).Delete(&K8sApp{}).Error
}

func (obj *K8sApp) CreateOne() error {
	return Db.Create(obj).Error

}

func (obj *K8sApp) UpdateOne() error {
	return Db.Where("id = ?", obj.ID).Updates(obj).Error

}

func (obj *K8sApp) DeleteOne() error {

	return Db.Select(clause.Associations).Unscoped().Delete(obj).Error

}

func (obj *K8sApp) UpdateFlowNodes(nodes []FlowNode) error {
	return Db.Model(obj).Association("FlowNodes").Replace(nodes)
}

func (obj *K8sApp) SetDefaultResource() {
	obj.CpuRequest = "0.1"
	obj.MemoryRequest = "10Mi"
	obj.CpuLimit = "1"
	obj.MemoryLimit = "1Gi"
}

func GetK8sAppAll() (objs []*K8sApp, err error) {
	err = Db.Find(&objs).Error
	return
}

func (obj *K8sApp) GenService() *corev1.Service {

	if len(obj.PortJsonFront) == 0 {
		return nil
	}

	// 新建svc svc是app维度的
	svc := &corev1.Service{}
	svc.Namespace = obj.Namespace
	svc.Name = obj.Name
	svc.Spec.Type = corev1.ServiceType(obj.ServiceType)
	// 端口是固定的
	svc.Spec.Ports = obj.PortJsonFront
	fixLabelM := map[string]string{
		common.K8S_APP_LABEL_STREE_NAME: obj.StreeNodeObj.NodePath,
		common.K8S_APP_LABEL_APP_NAME:   obj.Name,
	}

	svc.Spec.Selector = fixLabelM
	svc.Labels = fixLabelM
	return svc
}

// FillFrontAllData 后转前
func (obj *K8sApp) FillFrontAllData() error {
	dbUser, err := GetUserById(int(obj.UserID))
	if err != nil {
		return err
	}
	if dbUser != nil {
		obj.CreateUserName = dbUser.Username
	}

	// 找到所属项目
	dbProject, err := GetK8sProjectById(int(obj.K8sProjectID))
	if err != nil {
		return err
	}
	obj.ProjectObj = dbProject
	obj.K8sProjectName = dbProject.Name

	//把 服务树节点对象查询出来
	dbNode, err := GetStreeNodeById(int(obj.TreeNodeId))
	if err != nil {
		return err
	}
	obj.StreeNodeObj = dbNode
	obj.StreeNodeObj.FillFrontAllDataSimple()
	obj.NodePath = dbNode.NodePath
	// 填充cluster对象
	if obj.ClusterObj == nil {

		dbCluster, _ := GetK8sClusterByName(obj.ProjectObj.Cluster)
		//dbCluster.KubeConfigContent = ""
		if dbCluster != nil {
			obj.ClusterObj = dbCluster
		}
	}

	// 把volume 从json string 转化成数组返回给前端
	var vj []OneVolume
	if obj.VolumeJson != "" {
		err := json.Unmarshal([]byte(obj.VolumeJson), &vj)
		if err != nil {
			return err
		}
		obj.VolumeJsonFront = vj
	}

	// 把ports 从json string 转化成数组返回给前端
	var ps []corev1.ServicePort
	if obj.PortJson != "" {
		err := json.Unmarshal([]byte(obj.PortJson), &ps)
		if err != nil {
			return err
		}
		obj.PortJsonFront = ps
	}

	obj.Key = fmt.Sprintf("%d", obj.ID)
	// 把stringArray 转化一下

	obj.EnvsFront = common.GenFormKvFromStringArray(obj.Envs)
	obj.LabelsFront = common.GenFormKvFromStringArray(obj.Labels)
	obj.CommandsFront = common.GenFormKvFromStringArrayOnlyKey(obj.Commands)
	obj.ArgsFront = common.GenFormKvFromStringArrayOnlyKey(obj.Args)

	return nil

}

// 前转后
func (obj *K8sApp) FillDefaultData() (err error) {

	obj.Envs = common.GenStringArrayByFormKv(obj.EnvsFront)
	obj.Labels = common.GenStringArrayByFormKv(obj.LabelsFront)
	obj.Commands = common.GenStringArrayByFormKvOnlyKey(obj.CommandsFront)
	obj.Args = common.GenStringArrayByFormKvOnlyKey(obj.ArgsFront)

	vs, err := json.Marshal(obj.VolumeJsonFront)
	if err != nil {
		return err
	}
	obj.VolumeJson = string(vs)

	ps, err := json.Marshal(obj.PortJsonFront)
	if err != nil {
		return err
	}
	obj.PortJson = string(ps)

	return nil
}
func GetK8sAppCount() int {
	var count int64
	Db.Table("K8sApps").Count(&count)
	return int(count)
}

func GetK8sAppAllWithLimitOffset(limit, offset int) (objs []*K8sApp, err error) {
	err = Db.Preload("FlowNodes").Limit(limit).Offset(offset).Find(&objs).Error
	return
}

func GetK8sAppByClusterAndProjectId(cluster string, projectId int) (objs []*K8sApp, err error) {
	err = Db.Where("cluster = ? and k8s_project_id= ?", cluster, projectId).Preload("K8sInstances").Find(&objs).Error
	return
}

// select * from ecs where
func GetK8sAppByIdsWithLimitOffset(ids []int, limit, offset int) (objs []*K8sApp, err error) {
	err = Db.Limit(limit).Offset(offset).Preload("K8sInstances").Find(&objs, ids).Error
	return
}

func GetK8sAppByInstanceId(instanceId string) (*K8sApp, error) {

	var dbObj K8sApp
	err := Db.Where("instance_id = ?", instanceId).Preload("FlowNodes").First(&dbObj).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("K8sApp不存在")
		}
		return nil, fmt.Errorf("数据库错误:%w", err)
	}
	return &dbObj, nil

}

func GetK8sAppById(id int) (*K8sApp, error) {

	var dbObj K8sApp
	err := Db.Where("id = ?", id).First(&dbObj).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("K8sApp不存在")
		}
		return nil, fmt.Errorf("数据库错误:%w", err)
	}
	return &dbObj, nil

}

func GetK8sAppByName(name string) (*K8sApp, error) {

	var dbObj K8sApp
	err := Db.Where("name = ?", name).First(&dbObj).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("K8sApp不存在")
		}
		return nil, fmt.Errorf("数据库错误:%w", err)
	}
	return &dbObj, nil

}

func GetK8sAppByClusterAndName(cluster, name string) (*K8sApp, error) {

	var dbObj K8sApp
	err := Db.Where("name = ? and cluster=? ", name, cluster).First(&dbObj).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("K8sApp不存在")
		}
		return nil, fmt.Errorf("数据库错误:%w", err)
	}
	return &dbObj, nil

}

func GetK8sAppsByK8sProjectId(id int) (objs []*K8sApp, err error) {
	err = Db.Where("k8s_project_id = ?", id).Find(&objs).Error
	return
}
